############################# Tutorial Parser #################################

import os, sys, time, ast, _ast
from time import sleep
import re
from Queue import Queue, Empty
from threading import Thread

class Directions(object):
	def __init__(self, lines, groups):
		# Skip first line, and show the rest as paragraphs.
		self.paragraphs = []
		paragraph = None
		for line in lines:
			line = line.strip()
			if line == '':
				if paragraph:
					self.paragraphs.append(paragraph)
				paragraph = None
			else:
				if paragraph is None:
					paragraph = line
				else:
					paragraph += ' ' + line
		if paragraph:
			self.paragraphs.append(paragraph)

	def step(self, context, local_vars):
		context['guide'].add_text('\n\n'.join(self.paragraphs) + '\n\n')
		return True

class Pause(Directions):
	def __init__(self, lines, groups):
		# TODO: fix super decls elsewhere
		self.pause_name = 'Next'
		if len(groups) and groups[0]:
			self.pause_name = groups[0]
		super(Pause, self).__init__(lines, groups)

	def step(self, context, local_vars):
		if 'finished' in local_vars:
			context['guide'].add_text('----------------------\n')
			return super(Pause, self).step(context, local_vars)
		else:
			# TODO: set pause button
			context['app'].pause(self.pause_name)
			local_vars['finished'] = True
			return False

class GuideCode(object):
	def __init__(self, lines, groups):
		self.lines = lines
	
	def step(self, context, local_vars):
		context['guide'].add_text(''.join(self.lines) + '\n', 'code')
		return True

INITIAL_PYTHON_CODE = "import sys\nsys.stdin = console\nsys.stdout = console\n"

class PythonCode(object):
	def __init__(self, lines, groups):
		self.code = ''.join(lines)
		pass

	def step(self, context, local_vars):
		exec INITIAL_PYTHON_CODE + self.code in context
		return True

class PythonThread(object):
	"""
	Python code that is executed in a separate thread.
	"""
	def __init__(self, lines, groups):
		self.code = ''.join(lines)
		pass

	def step(self, context, local_vars):
		if 'started' in local_vars:
			return True
		else:
			local_vars['started'] = True
			thread = Thread(target=self.run_in_thread, args=[context], name='Lesson Code')
			thread.daemon = True
			thread.start()
			self.rejoin(thread, context, local_vars)
			# Once this thread has ended, the GUI will progress.
			return False
	
	def rejoin(self, thread, context, local_vars):
		# TODO: globals are bad
		if thread.is_alive():
			# Keep waiting
			context['app'].master.after(100, self.rejoin, thread, context, local_vars)
			# TODO: If the thread is running too long, give user the option to quit.
		else:
			context['next_step']()
	
	def run_in_thread(self, context):
		try:
			exec INITIAL_PYTHON_CODE + self.code in context
		except Exception as e:
			# TODO: make print thread-safe
			print e

class Checker(object):
	def __init__(self, lines, groups):
		pass

	def step(self, context, local_vars):
		return True

class Task(object):
	def __init__(self, lines, groups):
		pass

	def step(self, context, local_vars):
		return True

class Hint(object):
	def __init__(self, lines, groups):
		pass

	def step(self, context, local_vars):
		return True

class Example(object):
	def __init__(self, lines, groups):
		self.text = ''.join(lines)

	def step(self, context, local_vars):
		context['editor'].set_text(self.text)
		return True

LESSON_ITEMS = {
	r'!!!': PythonCode,
	r'\$\$\$': PythonThread,
	r'\.\.\.\s*(.*)': Pause,
	'--- Checker': Checker,
	'--- Task': Task,
	'--- Hint': Hint,
	'--- Example': Example,
	'--- Code': GuideCode,
	'---': Directions,
}

class Lesson(object):
	def __init__(self, lines):
		self.steps = []
		# Build a list of lesson steps
		current_item_type = None
		lesson_lines = []
		match_groups = []
		self.labels = {}
		for line in lines[1:]:
			match = re.match('^\:\:\:(\S*)\s*$', line)
			if match:
				# TODO: use group(x)
				label = match.groups()[0]
				print "Label: %s" % label
				self.labels[label] = len(self.steps)
			else:
				for lesson_item, item_type in LESSON_ITEMS.iteritems():
					match = re.match('^%s\s*$' % lesson_item, line)
					if match:
						if lesson_lines:
							if not current_item_type:
								current_item_type = Directions
								lesson_lines = lesson_lines
							self.steps.append(current_item_type(lesson_lines, match_groups))
						current_item_type = item_type
						lesson_lines = []
						match_groups = match.groups()
						break
				else:
					lesson_lines.append(line)
		if current_item_type:
			self.steps.append(current_item_type(lesson_lines, match_groups))

	def step(self, context, local_vars):
		step = local_vars.get('step')
		if step is None:
			local_vars['step'] = step = 0
		context['lesson'] = self
		while step < len(self.steps):
			item = self.steps[step]

			# Give the step a place to store local vars.
			step_local_vars = local_vars.get(step)
			if not step_local_vars:
				local_vars[step] = step_local_vars = {}

			# Attempt to run the step.  If it returns False,
			# that means the program is paused.
			if not item.step(context, step_local_vars):
				return False

			# Only advance if the caller didn't call goto
			if local_vars['step'] == step:
				local_vars['step'] = step = step + 1
		return True
	
	def goto(self, context, local_vars, label):
		if label not in self.labels:
			raise Exception("Cannot go to label %s because it does not exist." % label)
		sys.stderr.write("Going to label %s" % label)
		step = context['step'] = self.labels[label]
		# Remove locals for the following steps.
		for i in xrange(step + 1, len(self.steps)):
			if i in local_vars:
				del(local_vars[i])

class Chapter(object):
	def __init__(self, lines):
		# Get chapter information
		# Build a list of Lessons
		lesson_lines = None
		self.lessons = []
		for line in lines:
			if line[0:3] == '===':
				# Create a lesson object if we have lines for one
				if lesson_lines:
					self.lessons.append(Lesson(lines))
				# Begin a new lesson
				lesson_lines = [line]
			else:
				# Add lines to lesson
				lesson_lines.append(line)

		# Create a lesson object if we have lines for one
		if lesson_lines:
			self.lessons.append(Lesson(lines))
	
	def step(self, context, local_vars):
		# Run the root lesson(?)
		self.lessons[0].step(context, local_vars)

